Cloud Run ジョブの並列実行方法について考えてみた

Cloud Run ジョブの並列実行方法について考えてみた

Clock Icon2024.06.03

最近Cloud Run ジョブと真摯に向き合っている、データアナリティクス事業本部の根本です。
皆さんはCloud Run ジョブで並列処理を行なってバッチ処理の所要時間を短縮したいと思ったことはありませんか?今回はCloud Run ジョブでの並列処理の実行方法について考えてみました。同じようなことを考えている人はぜひ読んでみてください。

この記事の対象者

  • Cloud Run ジョブで並列処理を実装したいと考えているひと

Cloud Run ジョブでの並列処理のパターン

どのような並列処理のパターンがあるか

Cloud Run ジョブで並列処理を行う場合、以下の2パターンが考えられます(Workflowsからの呼び出しを例にしています)。

それぞれのパターンに関してメリットデメリットを考えてみました。

パターン メリット デメリット
1ジョブで複数Taskを起動 1つのジョブを起動すれば並列処理できる 1回の呼び出しで複数Taskをそれぞれ実行する実装が必要。環境変数を分割するなど呼び出し元、ジョブ側での環境変数の受け取り方も考える必要がある
1ジョブで1Taskを実行で複数起動 実装が容易 呼び出しもとで複数回呼び出す必要がある

それぞれのパターンを深掘りしてみましょう。

1つのジョブで複数のTaskを並列処理

この場合、ジョブの実装に注意する必要があります。
まず、Cloud Run ジョブのTaskを制御するにはCLOUD_RUN_TASK_INDEXという環境変数を用います。複数のTaskを並列処理する簡単な実装イメージとしては以下となります。

CLOUD_RUN_TASK_INDEX == 1:
  処理①
CLOUD_RUN_TASK_INDEX == 2:
  処理②

この場合の、向いているユースケースとしては以下が考えられます。

  • 各Taskで行いたい処理が決まっている場合
  • 呼び出しを一回で済ませたい場合(forループなどの反復処理を用いたくない)

1ジョブ1Taskで複数起動

この場合は、呼び出しもとの実装とジョブの環境変数に注意する必要があります。 Workflowsから並列Forループで起動すると仮定した場合、
1Taskで処理を行うので環境変数を用いて処理を可変にするようにしないと同一の処理が複数回起動されることになってしまいます。 例えば環境変数で$FILE_NAMEをスクリプトで用いていた場合、環境変数を実行時にオーバーライドして呼び出さないと同じ処理を複数回実行してしまいます。

環境変数のオーバーライドとはなんぞや、という方はこちらの記事も見てみてください。

実際の実装をもとに見てみましょう。

BUCKET_NAME="your-bucket-name"
FILE_NAME=$ENV_FILE_NAME
LOCAL_FILE_PATH="/tmp/$FILE_NAME"
COMPRESSED_FILE_PATH="/tmp/$FILE_NAME.gz"
# GCSからファイルをダウンロード
gsutil cp gs://$BUCKET_NAME/$FILE_NAME $LOCAL_FILE_PATH
# ファイルをgzipで圧縮
gzip -c $LOCAL_FILE_PATH > $COMPRESSED_FILE_PATH
# 圧縮ファイルをGCSにアップロード
gsutil cp $COMPRESSED_FILE_PATH gs://$BUCKET_NAME/$FILE_NAME.gz

上記スクリプトではENV_FILE_NAMEという環境変数をもとにCloud Storageからファイルをダウンロードしてgzip形式に圧縮して再度Cloud Storageにアップロードするという処理を行なっています。
このジョブを起動するWorkflowsを以下で実装します。

main:
    steps:
        - init:
            assign:
                - project_id: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
                - job_name: hello-cloud-run
                - job_location: asia-northeast1
                - list: ["test1.txt", "test2.txt", "test3.txt"]
        - loopFor:
            parallel:
              for:
                value: file_name
                in: ${list}
                steps:
                  - run_job:
                      call: googleapis.run.v1.namespaces.jobs.run
                      args:
                          name: ${"namespaces/" + project_id + "/jobs/" + job_name}
                          location: ${job_location}
                          body:
                            overrides:
                              containerOverrides:
                                env:
                                  - name: ENV_FILE_NAME
                                    value: ${file_name}
                      result: job_execution

Workflowsの実装としては、parallelステップ内でファイル名の配列をもとにForループを回し、ループ内でCloud Run ジョブを実行します。
ジョブ実行の際にはoverridesステップにてコンテナ環境変数をオーバーライドしてそれぞれのCloud Run ジョブを実行します。
このような実装をすることで、1つのジョブ1つのタスクで並列処理を実行することが可能となります。

この場合の、向いているユースケースとしては以下が考えられます。

  • 呼び出しもとで処理を出し分けたい場合
  • Clou Run ジョブの実装を簡潔なものにしたい場合

ただし、Workflowsから呼び出す場合はWorkflowsの並列処理の上限も考慮しなくてはいけないです。極度に並列数の大きいワークロードの場合、
複数Taskを持ったジョブをWorkflowsから呼び出すというパターン1と2の合わせ技のような実装も良いかもしれません。

まとめ

Cloud Run ジョブで並列処理をするパターンを考えてみました。私は、パターン2の1つのジョブを複数回呼び出す方がマイクロサービスぽいですし実装もシンプルになるので好きだなと考えました。とはいえ処理が決まっているのであればシンプルなのでパターン1も全然ありだなと思います。例えば1日一回特定ファイル群を並列処理する場合とか。それぞれのTaskに決まりきったファイルを処理させる実装をすれば良いので1ジョブで複数Taskを呼び分けることも容易と思います。
Cloud Run は考えれば考えるほど色々なことが可能で、本当に奥ゆかしいサービスだなと思います。今後もどんどん深掘りしていきたいです!
この記事がどなたかのお役に立てば嬉しいです。それではまた。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.